package optimizers.simplega;

import java.awt.GridLayout;
import java.text.DateFormat;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import java.util.Random;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import java.util.concurrent.TimeUnit;

import javax.swing.JFrame;
import javax.swing.JPanel;

import optimizers.commons.RunConfig;
import optimizers.commons.Static;
import optimizers.simplega.report.LoggerSGA;

import parallelism.SGAIndividualFitnessEvaluationTask;
import parallelism.SystemFitnessResult;

import util.MyUtil;
import visualization.GraphDisplayer;
import visualization.PlotExpData;

public abstract class GA {

	protected RunConfig config;

	public GA(RunConfig config) {
		this.config = config;
	}

	protected Random rand = new Random();
	ExecutorService eservice;
	CompletionService<Object> cservice;

	public abstract Individual createRandomIndividual();

	public void run() throws CloneNotSupportedException {
		DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd HH_mm_ss");
		Date date = new Date();
		String fileName = "result/SGA_" + dateFormat.format(date);
		Individual[] nextGen = new Individual[Constants.popSize];
		eservice = Executors.newFixedThreadPool(Constants.nParallelThreads);
		cservice = new ExecutorCompletionService<Object>(eservice);
		double[][] fitnessData = new double[4][Constants.generations + 1];
		double[][] templateData = new double[4][Constants.generations + 1];
		double[][] SDlogKData = new double[4][Constants.generations + 1];
		ArrayList<Individual> bestIndividualsLog = new ArrayList<Individual>();
		LoggerSGA logger = new LoggerSGA(fileName, true, fitnessData,
				templateData, SDlogKData, bestIndividualsLog);

		int taskCount;
		GraphDisplayer bestIndividuals = new GraphDisplayer(
				"Best Individual for each generations");

		/*
		 * JFrame speciesFrame = new JFrame("Species");
		 * speciesFrame.setSize(1300, 700); JTabbedPane tabbedPane = new
		 * JTabbedPane(); speciesFrame.add(tabbedPane);
		 * speciesFrame.setVisible(true);
		 */

		JFrame graphFrame = new JFrame("Metrics over generations");
		graphFrame.setSize(2400, 1200);
		GridLayout grid = new GridLayout(0, 1);
		JPanel panel = new JPanel();
		panel.setLayout(grid);
		grid.setColumns(2);
		graphFrame.add(panel);
		double bestFitness = 0;
		// generation loop
		for (int generation = 0; generation <= Constants.generations; generation++) {
			logger.logToMemory("Generation " + generation);
			if (generation == 0) { // initial generation
				for (int i = 0; i < Constants.popSize; i++) {
					nextGen[i] = createRandomIndividual();
					nextGen[i].generationIndex = generation;
					nextGen[i].individualIndex = i;
				}
			} else { // from 1st generation
				ArrayList<Individual> elites = selectElites(nextGen);
				int count = 0;
				double newBestFitness = elites.get(0).getFitness();
				for (int i = 0; i < elites.size(); i++) {
					nextGen[count] = clone(elites.get(i));
					nextGen[count].generationIndex = generation;
					nextGen[count].individualIndex = count;
					if (bestFitness < newBestFitness) {
						bestFitness = newBestFitness;
						if (nextGen[count].prune(cservice)) {
							count++;
							nextGen[count] = clone(elites.get(i));
							nextGen[count].generationIndex = generation;
							nextGen[count].individualIndex = count;
						}
					}
					count++;
				}
				for (int i = count; i < Constants.popSize; i++) {
					Individual parent = selectCrossoverParents(nextGen)[0];
					nextGen[i] = clone(parent);
					nextGen[i].mutate();
					nextGen[i].generationIndex = generation;
					nextGen[i].individualIndex = i;

				}
			}
			// Evaluate fitness in parallel
			taskCount = 0;
			for (int i = 0; i < nextGen.length; i++) {
				cservice.submit(new SGAIndividualFitnessEvaluationTask(i,
						nextGen[i]));
				taskCount++;
			}

			for (int i = 0; i < taskCount; i++) {
				try {
					Future<Object> future = cservice.poll(30, TimeUnit.SECONDS);
					if (future != null) {
						SystemFitnessResult result = (SystemFitnessResult) future
								.get();
						logger.logToMemory(i
								+ ":Indiv "
								+ result.index
								+ "(Fitness "
								+ nextGen[result.index].fitnessResult
										.toString()
								+ ")"
								+ " in "
								+ common.Static.df4
										.format(result.calculationTime) + "s");
					} else {
						logger.logToMemory(i + ":Drop");
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}

			int bestIndividualIndex = 0;
			for (int i = 1; i < nextGen.length; i++) {
				if (nextGen[bestIndividualIndex].getFitness() < nextGen[i]
						.getFitness()) {
					bestIndividualIndex = i;
				}
			}

			if (!Constants.SILENT_MODE) {
				bestIndividuals.addGraph(nextGen[bestIndividualIndex]
						.getOligoModel().getVisualization(250, 250),
						nextGen[bestIndividualIndex].displayTimeSereis(),
						nextGen[bestIndividualIndex].printModelInTextArea());
				bestIndividuals.display();
			}
			bestIndividualsLog.add(nextGen[bestIndividualIndex]);
			// display metrics
			Individual best1 = null;
			Individual best2 = null;
			Individual best3 = null;
			double avgFitness = 0;
			double avgTemplates = 0;
			double avgSD = 0;
			for (int i = 0; i < nextGen.length; i++) {
				if (best1 == null
						|| best1.getFitness() < nextGen[i].getFitness()) {
					best1 = nextGen[i];
				}
				avgFitness += nextGen[i].getFitness();
				avgTemplates += nextGen[i].getNumberOfTemplates();
				avgSD += nextGen[i].getStandardDeviationOfLogK();
			}
			avgFitness /= nextGen.length;
			avgTemplates /= nextGen.length;
			avgSD /= nextGen.length;
			for (int i = 0; i < nextGen.length; i++) {
				if (best2 == null
						|| best2.getFitness() < nextGen[i].getFitness()
						&& nextGen[i] != best1) {
					best2 = nextGen[i];
				}
			}
			for (int i = 0; i < nextGen.length; i++) {
				if (best3 == null
						|| best3.getFitness() < nextGen[i].getFitness()
						&& nextGen[i] != best1 && nextGen[i] != best2) {
					best3 = nextGen[i];
				}
			}
			fitnessData[0][generation] = best1.getFitness();
			fitnessData[1][generation] = best2.getFitness();
			fitnessData[2][generation] = best3.getFitness();
			fitnessData[3][generation] = avgFitness;

			templateData[0][generation] = best1.getNumberOfTemplates();
			templateData[1][generation] = best2.getNumberOfTemplates();
			templateData[2][generation] = best3.getNumberOfTemplates();
			templateData[3][generation] = avgTemplates;

			SDlogKData[0][generation] = best1.getStandardDeviationOfLogK();
			SDlogKData[1][generation] = best2.getStandardDeviationOfLogK();
			SDlogKData[2][generation] = best3.getStandardDeviationOfLogK();
			SDlogKData[3][generation] = avgSD;

			panel.removeAll();
			PlotExpData demo = new PlotExpData("Fitness over generations",
					fitnessData, new String[] { "Best 1", "Best 2", "Best 3",
							"Average" }, "Generation", "Fitness");
			PlotExpData demo1 = new PlotExpData(
					"Number of templates over generations", templateData,
					new String[] { "Best 1", "Best 2", "Best 3", "Average" },
					"Generation", "Number of templates");
			PlotExpData demo2 = new PlotExpData(
					"Standard deviation of logK over generations", SDlogKData,
					new String[] { "Best 1", "Best 2", "Best 3", "Average" },
					"Generation", "Standard deviation of logK");

			if (!Constants.SILENT_MODE) {
				panel.add(demo);
				panel.add(demo1);
				panel.add(demo2);
				graphFrame.setVisible(true);
			}
			logger.saveToFile();

		}
	}

	private ArrayList<Individual> selectElites(Individual[] curGen) {
		ArrayList<Individual> elites = new ArrayList<Individual>();
		for (int i = 0; i < Constants.popSize * Constants.elitismPercentage
				/ 100; i++) {
			Individual best = null;
			int start;
			for (start = 0; start < curGen.length; start++) {
				if (!elites.contains(curGen[start])) {
					best = curGen[start];
					break;
				}
			}
			for (int j = start + 1; j < curGen.length; j++) {
				if (best.getFitness() < curGen[j].getFitness()
						&& !elites.contains(curGen[j])) {
					best = curGen[j];
				}
			}
			elites.add(best);
			System.out.println("Elite " + i + ":" + best.getFitness() + " "
					+ best.individualIndex);
		}
		return elites;
	}

	private Individual[] selectCrossoverParents(Individual[] curGen) {
		double maxFitness = -Double.MAX_VALUE;
		Individual parent1 = null;
		Individual parent2 = null;
		do {
			for (int k = 0; k < Constants.popSize
					* Constants.tournamentSizePercentage / 100; k++) {
				Individual i = curGen[rand.nextInt(curGen.length)];
				if (i.getFitness() > maxFitness) {
					parent1 = i;
					maxFitness = i.getFitness();
				}
			}
			maxFitness = -Double.MAX_VALUE;

			for (int k = 0; k < Constants.popSize
					* Constants.tournamentSizePercentage / 100; k++) {
				Individual i = curGen[rand.nextInt(curGen.length)];
				if (i.getFitness() > maxFitness) {
					parent2 = i;
					maxFitness = i.getFitness();
				}
			}

		} while (parent1 == parent2);

		return new Individual[] { parent1, parent2 };
	}

	private Individual clone(Individual indiv)
			throws CloneNotSupportedException {
		Individual clone = createRandomIndividual();

		MyUtil.multiArrayCopy(indiv.seqK, clone.seqK);
		MyUtil.multiArrayCopy(indiv.seqConcentration, clone.seqConcentration);
		MyUtil.multiArrayCopy(indiv.inhK, clone.inhK);
		MyUtil.multiArrayCopy(indiv.templates, clone.templates);
		return clone;
	}

}
